package com.sia.springcloud.protocol.config;

import com.sia.springcloud.mapper.PointMapper;
import com.sia.springcloud.model.Device;
import com.sia.springcloud.model.Point;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * @Author: Sirius
 * @Date: 2021/12/21 1:51
 * @Description: 数采线程的配置类
 **/
@Slf4j
@Component
public class CollectJobConfig {

    /**
     * 存放改数采微服务中正在进行数采设备的状态，正在数采为true，否则为false
     * static volatile：保证唯一性和可见性
     */
    private static volatile HashMap<Integer, Boolean> deviceCollect = new HashMap<>();

    /**
     * 存放改数采微服务中正在进行数采点位的状态，正在数采为true，否则为false
     * static volatile：保证唯一性和可见性
     */
    private static volatile HashMap<Integer, Boolean> pointCollect = new HashMap<>();


    @Resource
    private Scheduler scheduler;


    @Resource
    private PointMapper pointMapper;


    /**
     * 开启数采
     *
     * @param device    开启设备对象
     * @param pointList 设备对应的点位对象集合
     * @return 返回一个数采是否开启成功数采
     * 返回true，说明该设备开启数采成功（至少有一个点位开启数采成功）
     * 返回false，说明该设备开启数采失败（全部点位开启数采失败（包含设备连接不上））
     */
    public boolean startCollect(Device device, List<Point> pointList, String topicName) {
        Integer deviceId = device.getId();

        //如果该设备是使用ip协议进行通信，则首先通过socket进行检测
        //首先尝试连接该设备，默认三次不能连接直接返回false
        if(device.getHost()!=null&&!"".equals(device.getHost())&&device.getPort()!=null&&device.getPort()!=0){
            int connectionLimit = Math.max(device.getConnectionLimit(), 3);
            for (int i = 0; i < connectionLimit; i++) {
                boolean connection = checkConnection(device);

                if (connection) {
                    log.info("设备 {} 第 {} 次连接成功", deviceId, i + 1);
                    break;
                } else {
                    log.error("设备 {} 第 {} 次连接失败", deviceId, i + 1);
                    if ((i + 1) >= connectionLimit) {
                        return false;
                    }
                }
            }
        }


        //开启数采，如果开启点位的数采任务为0，则直接返回false
        List<Integer> pointIdCollecting = startCollectSchedule(device, pointList, topicName);

        if (pointIdCollecting == null || pointIdCollecting.size() <= 0) {
            deviceCollect.put(deviceId, false);
            log.error("设备 {} 开启数采失败", deviceId);
            return false;
        } else {
            deviceCollect.put(deviceId, true);
            log.info("设备 {} 开启数采成功", deviceId);

            //更新数据库中该设备数采点位的数采状态
            for (Integer pointId : pointIdCollecting) {
                for (Point point : pointList) {
                    if (pointId.equals(point.getId())) {
                        point.setCollect(1);
                        pointMapper.updateById(point);
                    }
                }

            }
            return true;
        }
    }


    /**
     * 检查能否通过ip+端口连接上该数采设备
     *
     * @param device
     * @return 连接成功返回true，连接失败返回false
     */
    private boolean checkConnection(Device device) {
        String host = device.getHost();
        Integer port = device.getPort();

        Socket socket = new Socket();
        try {
            socket.connect(new InetSocketAddress(host, port));
        } catch (IOException e) {
            log.error("连接失败");
            log.error(e.getMessage());
            return false;
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                log.error("socket连接关闭失败");
                log.error(e.getMessage());
            }
        }
        return true;
    }

    /**
     * 开启某个设备中全部点位的数采任务
     *
     * @param device
     * @param pointList
     * @return 返回开启数采任务的点位的id数组
     * 如果List为null或者长度为0，说明该设备的点位数采任务全部失败
     * 否则说明至少有一个任务开启
     */
    private List<Integer> startCollectSchedule(Device device, List<Point> pointList, String destName) {

        List<Integer> pointIdCollectingList = new ArrayList<>();

        // 当该设备中有一个点位能够进行数采，那么就把这台设备的数采状态设置为true

        for (Point point : pointList) {

            pointCollect.put(point.getId(), false);

            try {
                //如果该任务已经存在，首先尝试删除
                JobKey jobKey = JobKey.jobKey(String.valueOf(point.getId()));
                if (scheduler.checkExists(jobKey)) {
                    scheduler.deleteJob(jobKey);
                }

                //配置得到这个点位数采任务的JobDetail和Trigger，然后开启这个任务
                JobDetail jobDetail = JobDetailConfig.newJobDetail(device, point, destName);
                Trigger trigger = TriggerConfig.newTrigger(point, jobDetail);
                scheduler.scheduleJob(jobDetail, trigger);

                pointIdCollectingList.add(point.getId());


                //这里不正确，因为测试发现，即使modbus slave没有开启，依然会正常开启这个任务，只不过任务会报错
                //为空可能是没有数据或者点位信息配置出现错误

                log.info("设备 {} 的点位 {} 正常开启了数采，并修改数采状态为TRUE", device.getId(), point.getId());
                pointCollect.put(point.getId(), true);


            } catch (SchedulerException e) {

                log.error("设备 {} 的点位 {} 开启数采过程中出现异常", device.getId(), point.getId());
                log.error(e.getMessage());
            }
        }

        log.info("设备 {} 中 {}/{} 个点位开启了数采", device.getId(), pointIdCollectingList.size(), pointList.size());
        return pointIdCollectingList;
    }


    /**
     * 停止某个设备全部点位的数采任务
     *
     * @param device
     * @param pointList
     * @return 没有返回值，基于尽力而为的原则，如果没有数采任务也会尝试关闭
     */
    public void stopCollect(Device device, List<Point> pointList) {
        deviceCollect.remove(device.getId());

        if (pointList == null || pointList.size() <= 0) {
            return;
        }
        for (Point point : pointList) {
            JobKey jobKey = JobKey.jobKey(String.valueOf(point.getId()));
            try {
                //点位数采状态map或数采任务中存在该点位，则进入尝试删除关闭
                if (pointCollect.containsKey(point.getId()) || scheduler.checkExists(jobKey)) {
                    scheduler.deleteJob(jobKey);
                    pointCollect.remove(point.getId());

                    point.setCollect(0);
                    pointMapper.updateById(point);
                }
            } catch (SchedulerException e) {
                log.error("删除设备 {} 的 {} 点位的数采任务时发生异常", device.getId(), point.getId());
                log.error(e.getMessage());
            }
        }
    }


}
